package archive.util

import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream


object Base91 {
    private const val ts = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\""

    fun encode(data: String) = ByteArrayOutputStream().let {
        encode(data.byteInputStream(), it)
        it.toString()
    }

    fun decode(data: String) = ByteArrayOutputStream().let {
        decode(data.byteInputStream(), it)
        it.toString()
    }

    fun encode(`is`: InputStream, os: OutputStream) {
        val ibuf = ByteArray(53248)
        val obuf = ByteArray(65536)
        val b91 = Coder()

        var s: Int = `is`.read(ibuf)
        while (s > 0) {
            s = b91.encode(ibuf, s, obuf)
            os.write(obuf, 0, s)
            s = `is`.read(ibuf)
        }
        s = b91.encEnd(obuf)
        os.write(obuf, 0, s)
    }

    fun decode(`is`: InputStream, os: OutputStream) {
        val ibuf = ByteArray(65536)
        val obuf = ByteArray(57344)
        val b91 = Coder()

        var s: Int = `is`.read(ibuf)
        while (s > 0) {
            s = b91.decode(ibuf, s, obuf)
            os.write(obuf, 0, s)
            s = `is`.read(ibuf)
        }
        s = b91.decEnd(obuf)
        os.write(obuf, 0, s)
    }

    private class Coder {
        var ebq: Int = 0
        var en: Int = 0
        var dbq: Int = 0
        var dn: Int = 0
        var dv: Int = -1
        val enctab: ByteArray = ts.toByteArray()
        val dectab: ByteArray = ByteArray(256)

        init {
            for (i in 0 until 256) dectab[i] = -1
            for (i in 0 until 91) dectab[enctab[i].toInt()] = i.toByte()
        }

        fun encode(ib: ByteArray, n: Int, ob: ByteArray): Int {
            var i = 0
            var c = 0

            while (i < n) {
                ebq = ebq or (ib[i].toInt() and 255 shl en)
                en += 8
                if (en > 13) {
                    var ev = ebq and 8191

                    if (ev > 88) {
                        ebq = ebq shr 13
                        en -= 13
                    } else {
                        ev = ebq and 16383
                        ebq = ebq shr 14
                        en -= 14
                    }
                    ob[c++] = enctab[ev % 91]
                    ob[c++] = enctab[ev / 91]
                }
                ++i
            }
            return c
        }

        fun encEnd(ob: ByteArray): Int {
            var c = 0

            if (en > 0) {
                ob[c++] = enctab[ebq % 91]
                if (en > 7 || ebq > 90)
                    ob[c++] = enctab[ebq / 91]
            }
            encReset()
            return c
        }

        fun encReset() {
            ebq = 0
            en = 0
        }

        fun decode(ib: ByteArray, n: Int, ob: ByteArray): Int {
            var i = 0
            var c = 0

            while (i < n) {
                if (dectab[ib[i].toInt()].toInt() == -1) {
                    ++i
                    continue
                }
                if (dv == -1)
                    dv = dectab[ib[i].toInt()].toInt()
                else {
                    dv += dectab[ib[i].toInt()] * 91
                    dbq = dbq or (dv shl dn)
                    dn += if (dv and 8191 > 88) 13 else 14
                    do {
                        ob[c++] = dbq.toByte()
                        dbq = dbq shr 8
                        dn -= 8
                    } while (dn > 7)
                    dv = -1
                }
                ++i
            }
            return c
        }

        fun decEnd(ob: ByteArray): Int {
            var c = 0

            if (dv != -1)
                ob[c++] = (dbq or (dv shl dn)).toByte()
            decReset()
            return c
        }

        fun decReset() {
            dbq = 0
            dn = 0
            dv = -1
        }
    }
}

